| 1 | import type { APIContext, GetStaticPathsResult } from "astro"; |
| 2 | import { getCollection, getEntryBySlug } from "astro:content"; |
| 3 | import satori, { SatoriOptions } from "satori"; |
| 4 | import { html } from "satori-html"; |
| 5 | import { Resvg } from "@resvg/resvg-js"; |
| 6 | import siteConfig from "@/site-config"; |
| 7 | import { getFormattedDate } from "@/utils"; |
| 8 | |
| 9 | const monoFontReg = await fetch( |
| 10 | "https://api.fontsource.org/v1/fonts/roboto-mono/latin-400-normal.ttf" |
| 11 | ); |
| 12 | |
| 13 | const monoFontBold = await fetch( |
| 14 | "https://api.fontsource.org/v1/fonts/roboto-mono/latin-700-normal.ttf" |
| 15 | ); |
| 16 | |
| 17 | const ogOptions: SatoriOptions = { |
| 18 | width: 1200, |
| 19 | height: 630, |
| 20 | // debug: true, |
| 21 | embedFont: true, |
| 22 | fonts: [ |
| 23 | { |
| 24 | name: "Roboto Mono", |
| 25 | data: await monoFontReg.arrayBuffer(), |
| 26 | weight: 400, |
| 27 | style: "normal", |
| 28 | }, |
| 29 | { |
| 30 | name: "Roboto Mono", |
| 31 | data: await monoFontBold.arrayBuffer(), |
| 32 | weight: 700, |
| 33 | style: "normal", |
| 34 | }, |
| 35 | ], |
| 36 | }; |
| 37 | |
| 38 | const markup = (title: string, pubDate: string, description: string) => html`<div |
| 39 | tw="flex flex-col w-full h-full bg-[#191724] text-[#e0def4]" |
| 40 | > |
| 41 | <div tw="flex flex-col flex-1 w-full p-10 justify-center"> |
| 42 | <p tw="text-2xl mb-6">${pubDate}</p> |
| 43 | <h1 tw="text-6xl font-bold leading-snug text-white">${title}</h1> |
| 44 | <h2 tw="text-2xl font-bold leading-snug text-white">${description}</h2> |
| 45 | </div> |
| 46 | <div tw="flex items-center justify-between w-full p-10 border-t border-[#7c6f64] text-xl"> |
| 47 | <div tw="flex items-center"> |
| 48 | |
| 49 | <svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 24 24" stroke-width="1.5" height="60"> |
| 50 | <path stroke-linecap="round" stroke-linejoin="round" d="M2.25 15a4.5 4.5 0 004.5 4.5H18a3.75 3.75 0 001.332-7.257 3 3 0 00-3.758-3.848 5.25 5.25 0 00-10.233 2.33A4.502 4.502 0 002.25 15z" /> |
| 51 | </svg> |
| 52 | <p tw="ml-3 font-semibold text-3xl">${siteConfig.title}</p> |
| 53 | </div> |
| 54 | </div> |
| 55 | </div>`; |
| 56 | |
| 57 | export async function get({ params: { slug } }: APIContext) { |
| 58 | const post = await getEntryBySlug("post", slug!); |
| 59 | const title = post?.data.title ?? siteConfig.title; |
| 60 | const postDate = getFormattedDate(post?.data.publishDate ?? Date.now(), { |
| 61 | weekday: "long", |
| 62 | }); |
| 63 | const description = post?.data.description ?? siteConfig.title; |
| 64 | const svg = await satori(markup(title, postDate, description), ogOptions); |
| 65 | const png = new Resvg(svg).render().asPng(); |
| 66 | return { |
| 67 | body: png, |
| 68 | encoding: "binary", |
| 69 | }; |
| 70 | } |
| 71 | |
| 72 | export async function getStaticPaths(): Promise<GetStaticPathsResult> { |
| 73 | const posts = await getCollection("post"); |
| 74 | return posts.filter(({ data }) => !data.ogImage).map(({ slug }) => ({ params: { slug } })); |
| 75 | } |